package cn.redragon.soa.common.util;

import static java.util.Collections.emptyList;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class JsonUtil {

    private static ObjectMapper objectMapper;

    private static String EMPTY_OBJECT_STR = "{}";

    @Autowired
    public JsonUtil(@Qualifier("commonObjectMapper") ObjectMapper objectMapper) {
        JsonUtil.objectMapper = objectMapper;
    }

    /**
     * Json to object.
     *
     * @param <T>   the generic type
     * @param json  the json
     * @param clazz the clazz
     * @return the t
     */
    public static <T> T jsonToObject(String json, Class<T> clazz) {
        try {
            return getMapperInstance().readValue(json, clazz);
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Json to object.
     *
     * @param <T>   the generic type
     * @param json  the jsonNode
     * @param clazz the clazz
     * @return the t
     */
    public static <T> T jsonToObject(JsonNode json, Class<T> clazz) {
        try {
            return getMapperInstance().treeToValue(json, clazz);
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Convert Json to the list of Object
     *
     * @param <T>   the generic type
     * @param json  the json
     * @param clazz the clazz
     * @return The list of T
     */
    public static <T> List<T> jsonToObjList(JsonNode json, Class<T> clazz) {
        try {
            if (json.isArray()) {
                return getMapperInstance().readValue(json.toString(), getListType(clazz));
            } else {
                return emptyList();
            }
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Convert Json to the list of Object
     *
     * @param <T>        the generic type
     * @param jsonString the json string
     * @param clazz      the clazz
     * @return The list of T
     */
    public static <T> List<T> jsonToObjList(String jsonString, Class<T> clazz) {
        try {
            return getMapperInstance().readValue(jsonString, getListType(clazz));
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Convert Json to the linked list of Object
     *
     * @param <T>        the generic type
     * @param jsonString the json string
     * @param clazz      the clazz
     * @return The list of T
     */
    public static <T> List<T> jsonToLinkedList(String jsonString, Class<T> clazz) {
        try {
            return getMapperInstance().readValue(jsonString, getLinkedListType(clazz));
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Convert Json to the Map of Object
     *
     * @param jsonString
     * @param valueClazz
     * @param <V>
     * @return
     */
    public static <V> Map<String, V> jsonToObjMap(String jsonString, Class<V> valueClazz) {
        try {
            return getMapperInstance().readValue(jsonString, getMapType(getJavaType(String.class), getJavaType(valueClazz)));
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Convert Json to the Map of Object  eg: Map<String, List<T>>
     *
     * @param jsonString
     * @param keyClazz
     * @param valueClazz
     * @param <K, V>
     * @return
     */
    public static <K, V> Map<K, List<V>> jsonToObjMapList(String jsonString, Class<K> keyClazz, Class<V> valueClazz) {
        try {
            return getMapperInstance().readValue(jsonString, getMapType(getJavaType(keyClazz), getListType(valueClazz)));
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    public static <V> Map<String, List<V>> jsonToObjMapList(String jsonString, Class<V> valueClazz) {
        try {
            return getMapperInstance().readValue(jsonString, getMapType(getJavaType(String.class), getListType(valueClazz)));
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Object to json.
     *
     * @param object the object
     * @return the string
     */
    public static String objectToJsonStr(Object object) {
        try {
            return getMapperInstance().setSerializationInclusion(Include.NON_NULL)
                .writeValueAsString(object);
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * Nullable object to json.
     *
     * @param object the object
     * @return the string
     */
    public static String objectToJsonStrNullable(Object object) {
        if (object == null) {
            return EMPTY_OBJECT_STR;
        }
        return objectToJsonStr(object);
    }

    /**
     * String to JsonNode
     *
     * @param jsonStr the String
     * @return JsonNode
     */
    public static JsonNode stringToJsonNode(String jsonStr) {
        try {
            return getMapperInstance().setSerializationInclusion(Include.NON_NULL)
                .readTree(jsonStr);
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    public static <T extends JsonNode> T objectToJson(Object object) {
        return getMapperInstance().setSerializationInclusion(Include.NON_NULL).valueToTree(object);
    }

    public static <T extends JsonNode> T objectToJsonIncludingNull(Object object) {
        return getMapperInstance().setSerializationInclusion(Include.ALWAYS).valueToTree(object);
    }

    /**
     * Object to json with subTypes registration.
     *
     * @param <T>            the generic type
     * @param object         the object
     * @param subTypeClasses the sub type classes
     * @return the string
     */
    @SafeVarargs
    public static <T> String objectToJsonExtended(Object object, Class<T>... subTypeClasses) {
        try {
            ObjectMapper mapper = getMapperInstance();
            mapper.registerSubtypes(subTypeClasses);
            return mapper.writeValueAsString(object);
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    public static JsonNode listToJsonNode(List list) {
        return getMapperInstance().setSerializationInclusion(Include.NON_NULL).valueToTree(list);
    }

    /**
     * Creates the json object.
     *
     * @return the object node
     */
    public static ObjectNode newJSONObject() {
        return getMapperInstance().createObjectNode();
    }

    public static JsonNode processTextNode(JsonNode jsonNode) {
        if (!jsonNode.isTextual()) {
            return jsonNode;
        }

        TextNode text = (TextNode) jsonNode;
        jsonNode = JsonUtil.stringToJsonNode(text.asText());

        return jsonNode;
    }

    public static <T, S> S convertValue(T object, Class<S> sClass) {
        return getMapperInstance().convertValue(object, sClass);
    }

    public static ObjectMapper getObjectMapper() {
        return objectMapper;
    }

    /**
     * New json array.
     *
     * @return the array node
     */
    public static ArrayNode newJSONArray() {
        return getMapperInstance().createArrayNode();
    }

    private static ObjectMapper getMapperInstance() {
        return objectMapper;
    }

    private static CollectionType getListType(Class<?> target) {
        return getMapperInstance().getTypeFactory().constructCollectionType(List.class, target);
    }

    private static CollectionType getLinkedListType(Class<?> target) {
        return getMapperInstance().getTypeFactory().constructCollectionType(LinkedList.class, target);
    }

    private static JavaType getJavaType(Class<?> target) {
        return getMapperInstance().getTypeFactory().constructType(target);
    }

    private static MapType getMapType(JavaType keyType, CollectionType valueType) {
        return getMapperInstance().getTypeFactory().constructMapType(Map.class, keyType, valueType);
    }

    private static MapType getMapType(JavaType keyType, JavaType valueType) {
        return getMapperInstance().getTypeFactory().constructMapType(Map.class, keyType, valueType);
    }
}
